home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / Other Langs / Tickle-4.0 (tcl) / tcl / src / tclHistory.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-11-07  |  30.8 KB  |  1,131 lines  |  [TEXT/MPS ]

  1. #ifdef MPW
  2. #    pragma segment TCL_HISTORY
  3. #endif
  4.  
  5. /* 
  6.  * tclHistory.c --
  7.  *
  8.  *    This module implements history as an optional addition to Tcl.
  9.  *    It can be called to record commands ("events") before they are
  10.  *    executed, and it provides a command that may be used to perform
  11.  *    history substitutions.
  12.  *
  13.  * Copyright (c) 1990-1993 The Regents of the University of California.
  14.  * All rights reserved.
  15.  *
  16.  * Permission is hereby granted, without written agreement and without
  17.  * license or royalty fees, to use, copy, modify, and distribute this
  18.  * software and its documentation for any purpose, provided that the
  19.  * above copyright notice and the following two paragraphs appear in
  20.  * all copies of this software.
  21.  * 
  22.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  23.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  24.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  25.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26.  *
  27.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  28.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  29.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  30.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  31.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  32.  */
  33.  
  34. #ifndef lint
  35. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclHistory.c,v 1.29 93/08/18 16:07:56 ouster Exp $ SPRITE (Berkeley)";
  36. #endif /* not lint */
  37.  
  38. #include "tclInt.h"
  39.  
  40. /*
  41.  * This history stuff is mostly straightforward, except for one thing
  42.  * that makes everything very complicated.  Suppose that the following
  43.  * commands get executed:
  44.  *    echo foo
  45.  *    history redo
  46.  * It's important that the history event recorded for the second command
  47.  * be "echo foo", not "history redo".  Otherwise, if another "history redo"
  48.  * command is typed, it will result in infinite recursions on the
  49.  * "history redo" command.  Thus, the actual recorded history must be
  50.  *    echo foo
  51.  *    echo foo
  52.  * To do this, the history command revises recorded history as part of
  53.  * its execution.  In the example above, when "history redo" starts
  54.  * execution, the current event is "history redo", but the history
  55.  * command arranges for the current event to be changed to "echo foo".
  56.  *
  57.  * There are three additional complications.  The first is that history
  58.  * substitution may only be part of a command, as in the following
  59.  * command sequence:
  60.  *    echo foo bar
  61.  *    echo [history word 3]
  62.  * In this case, the second event should be recorded as "echo bar".  Only
  63.  * part of the recorded event is to be modified.  Fortunately, Tcl_Eval
  64.  * helps with this by recording (in the evalFirst and evalLast fields of
  65.  * the intepreter) the location of the command being executed, so the
  66.  * history module can replace exactly the range of bytes corresponding
  67.  * to the history substitution command.
  68.  *
  69.  * The second complication is that there are two ways to revise history:
  70.  * replace a command, and replace the result of a command.  Consider the
  71.  * two examples below:
  72.  *    format {result is %d} $num       |    format {result is %d} $num
  73.  *    print [history redo]           |    print [history word 3]
  74.  * Recorded history for these two cases should be as follows:
  75.  *    format {result is %d} $num       |    format {result is %d} $num
  76.  *    print [format {result is %d} $num] |    print $num
  77.  * In the left case, the history command was replaced with another command
  78.  * to be executed (the brackets were retained), but in the case on the
  79.  * right the result of executing the history command was replaced (i.e.
  80.  * brackets were replaced too).
  81.  *
  82.  * The third complication is that there could potentially be many
  83.  * history substitutions within a single command, as in:
  84.  *    echo [history word 3] [history word 2]
  85.  * There could even be nested history substitutions, as in:
  86.  *    history subs abc [history word 2]
  87.  * If history revisions were made immediately during each "history" command
  88.  * invocations, it would be very difficult to produce the correct cumulative
  89.  * effect from several substitutions in the same command.  To get around
  90.  * this problem, the actual history revision isn't made during the execution
  91.  * of the "history" command.  Information about the changes is just recorded,
  92.  * in xxx records, and the actual changes are made during the next call to
  93.  * Tcl_RecordHistory (when we know that execution of the previous command
  94.  * has finished).
  95.  */
  96.  
  97. /*
  98.  * Default space allocation for command strings:
  99.  */
  100.  
  101. #define INITIAL_CMD_SIZE 40
  102.  
  103. /*
  104.  * Forward declarations for procedures defined later in this file:
  105.  */
  106.  
  107. static void        DoRevs _ANSI_ARGS_((Interp *iPtr));
  108. static HistoryEvent *    GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));
  109. static char *        GetWords _ANSI_ARGS_((Interp *iPtr, char *command,
  110.                 char *words));
  111. static void        InitHistory _ANSI_ARGS_((Interp *iPtr));
  112. static void        InsertRev _ANSI_ARGS_((Interp *iPtr,
  113.                 HistoryRev *revPtr));
  114. static void        MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));
  115. static void        RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));
  116. static void        RevResult _ANSI_ARGS_((Interp *iPtr, char *string));
  117. static int        SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,
  118.                 char *old, char *new));
  119.  
  120. /*
  121.  *----------------------------------------------------------------------
  122.  *
  123.  * InitHistory --
  124.  *
  125.  *    Initialize history-related state in an interpreter.
  126.  *
  127.  * Results:
  128.  *    None.
  129.  *
  130.  * Side effects:
  131.  *    History info is initialized in iPtr.
  132.  *
  133.  *----------------------------------------------------------------------
  134.  */
  135.  
  136. static void
  137. InitHistory(iPtr)
  138.     register Interp *iPtr;        /* Interpreter to initialize. */
  139. {
  140.     int i;
  141.  
  142.     if (iPtr->numEvents != 0) {
  143.     return;
  144.     }
  145.     iPtr->numEvents = 20;
  146.     iPtr->events = (HistoryEvent *)
  147.         ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));
  148.     for (i = 0; i < iPtr->numEvents; i++) {
  149.     iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  150.     *iPtr->events[i].command = 0;
  151.     iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;
  152.     }
  153.     iPtr->curEvent = 0;
  154.     iPtr->curEventNum = 0;
  155. }
  156.  
  157. /*
  158.  *----------------------------------------------------------------------
  159.  *
  160.  * Tcl_RecordAndEval --
  161.  *
  162.  *    This procedure adds its command argument to the current list of
  163.  *    recorded events and then executes the command by calling Tcl_Eval.
  164.  *
  165.  * Results:
  166.  *    The return value is a standard Tcl return value, the result of
  167.  *    executing cmd.
  168.  *
  169.  * Side effects:
  170.  *    The command is recorded and executed.  In addition, pending history
  171.  *    revisions are carried out, and information is set up to enable
  172.  *    Tcl_Eval to identify history command ranges.  This procedure also
  173.  *    initializes history information for the interpreter, if it hasn't
  174.  *    already been initialized.
  175.  *
  176.  *----------------------------------------------------------------------
  177.  */
  178.  
  179. int
  180. Tcl_RecordAndEval(interp, cmd, flags)
  181.     Tcl_Interp *interp;        /* Token for interpreter in which command
  182.                  * will be executed. */
  183.     char *cmd;            /* Command to record. */
  184.     int flags;            /* Additional flags to pass to Tcl_Eval. 
  185.                  * TCL_NO_EVAL means only record: don't
  186.                  * execute command. */
  187. {
  188.     register Interp *iPtr = (Interp *) interp;
  189.     register HistoryEvent *eventPtr;
  190.     int length, result;
  191.  
  192.     if (iPtr->numEvents == 0) {
  193.     InitHistory(iPtr);
  194.     }
  195.     DoRevs(iPtr);
  196.  
  197.     /*
  198.      * Don't record empty commands.
  199.      */
  200.  
  201.     while (isspace(UCHAR(*cmd))) {
  202.     cmd++;
  203.     }
  204.     if (*cmd == '\0') {
  205.     Tcl_ResetResult(interp);
  206.     return TCL_OK;
  207.     }
  208.  
  209.     iPtr->curEventNum++;
  210.     iPtr->curEvent++;
  211.     if (iPtr->curEvent >= iPtr->numEvents) {
  212.     iPtr->curEvent = 0;
  213.     }
  214.     eventPtr = &iPtr->events[iPtr->curEvent];
  215.  
  216.     /*
  217.      * Chop off trailing newlines before recording the command.
  218.      */
  219.  
  220.     length = strlen(cmd);
  221. #if defined(THINK_C) && defined(TCLAPPL)
  222.     while (cmd[length-1] == '\r')
  223. #else
  224.     while (cmd[length-1] == '\n')
  225. #endif
  226.         {
  227.         length--;
  228.         }
  229.     MakeSpace(eventPtr, length + 1);
  230.     strncpy(eventPtr->command, cmd, length);
  231.     eventPtr->command[length] = 0;
  232.  
  233.     /*
  234.      * Execute the command.  Note: history revision isn't possible after
  235.      * a nested call to this procedure, because the event at the top of
  236.      * the history list no longer corresponds to what's going on when
  237.      * a nested call here returns.  Thus, must leave history revision
  238.      * disabled when we return.
  239.      */
  240.  
  241.     result = TCL_OK;
  242.     if (flags != TCL_NO_EVAL) {
  243.     iPtr->historyFirst = cmd;
  244.     iPtr->revDisables = 0;
  245.     iPtr->evalFlags = flags | TCL_RECORD_BOUNDS;
  246.     result = Tcl_Eval(interp, cmd);
  247.     }
  248.     iPtr->revDisables = 1;
  249.     return result;
  250. }
  251.  
  252. /*
  253.  *----------------------------------------------------------------------
  254.  *
  255.  * Tcl_HistoryCmd --
  256.  *
  257.  *    This procedure is invoked to process the "history" Tcl command.
  258.  *    See the user documentation for details on what it does.
  259.  *
  260.  * Results:
  261.  *    A standard Tcl result.
  262.  *
  263.  * Side effects:
  264.  *    See the user documentation.
  265.  *
  266.  *----------------------------------------------------------------------
  267.  */
  268.  
  269.     /* ARGSUSED */
  270. int
  271. Tcl_HistoryCmd(dummy, interp, argc, argv)
  272.     ClientData dummy;            /* Not used. */
  273.     Tcl_Interp *interp;            /* Current interpreter. */
  274.     int argc;                /* Number of arguments. */
  275.     char **argv;            /* Argument strings. */
  276. {
  277.     register Interp *iPtr = (Interp *) interp;
  278.     register HistoryEvent *eventPtr;
  279.     int length;
  280.     char c;
  281.  
  282.     if (iPtr->numEvents == 0) {
  283.     InitHistory(iPtr);
  284.     }
  285.  
  286.     /*
  287.      * If no arguments, treat the same as "history info".
  288.      */
  289.  
  290.     if (argc == 1) {
  291.     goto infoCmd;
  292.     }
  293.  
  294.     c = argv[1][0];
  295.     length = strlen(argv[1]);
  296.  
  297.     if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {
  298.     if ((argc != 3) && (argc != 4)) {
  299.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  300.             " add event ?exec?\"", (char *) NULL);
  301.         return TCL_ERROR;
  302.     }
  303.     if (argc == 4) {
  304.         if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {
  305.         Tcl_AppendResult(interp, "bad argument \"", argv[3],
  306.             "\": should be \"exec\"", (char *) NULL);
  307.         return TCL_ERROR;
  308.         }
  309.         return Tcl_RecordAndEval(interp, argv[2], 0);
  310.     }
  311.     return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);
  312.     } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {
  313.     if ((argc != 3) && (argc != 4)) {
  314.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  315.             " change newValue ?event?\"", (char *) NULL);
  316.         return TCL_ERROR;
  317.     }
  318.     if (argc == 3) {
  319.         eventPtr = &iPtr->events[iPtr->curEvent];
  320.         iPtr->revDisables += 1;
  321.         while (iPtr->revPtr != NULL) {
  322.         HistoryRev *nextPtr;
  323.  
  324.         ckfree(iPtr->revPtr->newBytes);
  325.         nextPtr = iPtr->revPtr->nextPtr;
  326.         ckfree((char *) iPtr->revPtr);
  327.         iPtr->revPtr = nextPtr;
  328.         }
  329.     } else {
  330.         eventPtr = GetEvent(iPtr, argv[3]);
  331.         if (eventPtr == NULL) {
  332.         return TCL_ERROR;
  333.         }
  334.     }
  335.     MakeSpace(eventPtr, strlen(argv[2]) + 1);
  336.     strcpy(eventPtr->command, argv[2]);
  337.     return TCL_OK;
  338.     } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {
  339.     if (argc > 3) {
  340.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  341.             " event ?event?\"", (char *) NULL);
  342.         return TCL_ERROR;
  343.     }
  344.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  345.     if (eventPtr == NULL) {
  346.         return TCL_ERROR;
  347.     }
  348.     RevResult(iPtr, eventPtr->command);
  349.     Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);
  350.     return TCL_OK;
  351.     } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {
  352.     int count, indx, i;
  353.     char *newline;
  354.  
  355.     if ((argc != 2) && (argc != 3)) {
  356.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  357.             " info ?count?\"", (char *) NULL);
  358.         return TCL_ERROR;
  359.     }
  360.     infoCmd:
  361.     if (argc == 3) {
  362.         if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  363.         return TCL_ERROR;
  364.         }
  365.         if (count > iPtr->numEvents) {
  366.         count = iPtr->numEvents;
  367.         }
  368.     } else {
  369.         count = iPtr->numEvents;
  370.     }
  371.     newline = "";
  372.     for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;
  373.         i < count; i++, indx++) {
  374.         char *cur, *next, savedChar;
  375.         char serial[20];
  376.  
  377.         if (indx >= iPtr->numEvents) {
  378.         indx -= iPtr->numEvents;
  379.         }
  380.         cur = iPtr->events[indx].command;
  381.         if (*cur == '\0') {
  382.         continue;        /* No command recorded here. */
  383.         }
  384.         sprintf(serial, "%6d  ", iPtr->curEventNum + 1 - (count - i));
  385.         Tcl_AppendResult(interp, newline, serial, (char *) NULL);
  386. #if defined(THINK_C) && defined(TCLAPPL)
  387.         newline = "\r";
  388. #else
  389.         newline = "\n";
  390. #endif
  391.  
  392.         /*
  393.          * Tricky formatting here:  for multi-line commands, indent
  394.          * the continuation lines.
  395.          */
  396.  
  397.         while (1) {
  398. #if defined(THINK_C) && defined(TCLAPPL)
  399.         next = strchr(cur, '\r');
  400. #else
  401.         next = strchr(cur, '\n');
  402. #endif
  403.         if (next == NULL) {
  404.             break;
  405.         }
  406.         next++;
  407.         savedChar = *next;
  408.         *next = 0;
  409.         Tcl_AppendResult(interp, cur, "\t", (char *) NULL);
  410.         *next = savedChar;
  411.         cur = next;
  412.         }
  413.         Tcl_AppendResult(interp, cur, (char *) NULL);
  414.     }
  415.     return TCL_OK;
  416.     } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {
  417.     int count, i, src;
  418.     HistoryEvent *events;
  419.  
  420.     if (argc != 3) {
  421.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  422.             " keep number\"", (char *) NULL);
  423.         return TCL_ERROR;
  424.     }
  425.     if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  426.         return TCL_ERROR;
  427.     }
  428.     if ((count <= 0) || (count > 1000)) {
  429.         Tcl_AppendResult(interp, "illegal keep count \"", argv[2],
  430.             "\"", (char *) NULL);
  431.         return TCL_ERROR;
  432.     }
  433.  
  434.     /*
  435.      * Create a new history array and copy as much existing history
  436.      * as possible from the old array.
  437.      */
  438.  
  439.     events = (HistoryEvent *)
  440.         ckalloc((unsigned) (count * sizeof(HistoryEvent)));
  441.     if (count < iPtr->numEvents) {
  442.         src = iPtr->curEvent + 1 - count;
  443.         if (src < 0) {
  444.         src += iPtr->numEvents;
  445.         }
  446.     } else {
  447.         src = iPtr->curEvent + 1;
  448.     }
  449.     for (i = 0; i < count; i++, src++) {
  450.         if (src >= iPtr->numEvents) {
  451.         src = 0;
  452.         }
  453.         if (i < iPtr->numEvents) {
  454.         events[i] = iPtr->events[src];
  455.         iPtr->events[src].command = NULL;
  456.         } else {
  457.         events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  458.         events[i].command[0] = 0;
  459.         events[i].bytesAvl = INITIAL_CMD_SIZE;
  460.         }
  461.     }
  462.  
  463.     /*
  464.      * Throw away everything left in the old history array, and
  465.      * substitute the new one for the old one.
  466.      */
  467.  
  468.     for (i = 0; i < iPtr->numEvents; i++) {
  469.         if (iPtr->events[i].command != NULL) {
  470.         ckfree(iPtr->events[i].command);
  471.         }
  472.     }
  473.     ckfree((char *) iPtr->events);
  474.     iPtr->events = events;
  475.     if (count < iPtr->numEvents) {
  476.         iPtr->curEvent = count-1;
  477.     } else {
  478.         iPtr->curEvent = iPtr->numEvents-1;
  479.     }
  480.     iPtr->numEvents = count;
  481.     return TCL_OK;
  482.     } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {
  483.     if (argc != 2) {
  484.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  485.             " nextid\"", (char *) NULL);
  486.         return TCL_ERROR;
  487.     }
  488.     sprintf(iPtr->result, "%d", iPtr->curEventNum+1);
  489.     return TCL_OK;
  490.     } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {
  491.     if (argc > 3) {
  492.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  493.             " redo ?event?\"", (char *) NULL);
  494.         return TCL_ERROR;
  495.     }
  496.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  497.     if (eventPtr == NULL) {
  498.         return TCL_ERROR;
  499.     }
  500.     RevCommand(iPtr, eventPtr->command);
  501.     return Tcl_Eval(interp, eventPtr->command);
  502.     } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {
  503.     if ((argc > 5) || (argc < 4)) {
  504.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  505.             " substitute old new ?event?\"", (char *) NULL);
  506.         return TCL_ERROR;
  507.     }
  508.     eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);
  509.     if (eventPtr == NULL) {
  510.         return TCL_ERROR;
  511.     }
  512.     return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);
  513.     } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {
  514.     char *words;
  515.  
  516.     if ((argc != 3) && (argc != 4)) {
  517.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  518.             " words num-num/pat ?event?\"", (char *) NULL);
  519.         return TCL_ERROR;
  520.     }
  521.     eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);
  522.     if (eventPtr == NULL) {
  523.         return TCL_ERROR;
  524.     }
  525.     words = GetWords(iPtr, eventPtr->command, argv[2]);
  526.     if (words == NULL) {
  527.         return TCL_ERROR;
  528.     }
  529.     RevResult(iPtr, words);
  530.     iPtr->result = words;
  531.     iPtr->freeProc = (Tcl_FreeProc *) free;
  532.     return TCL_OK;
  533.     }
  534.  
  535.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  536.         "\": must be add, change, event, info, keep, nextid, ",
  537.         "redo, substitute, or words", (char *) NULL);
  538.     return TCL_ERROR;
  539. }
  540.  
  541. /*
  542.  *----------------------------------------------------------------------
  543.  *
  544.  * MakeSpace --
  545.  *
  546.  *    Given a history event, make sure it has enough space for
  547.  *    a string of a given length (enlarge the string area if
  548.  *    necessary).
  549.  *
  550.  * Results:
  551.  *    None.
  552.  *
  553.  * Side effects:
  554.  *    More memory may get allocated.
  555.  *
  556.  *----------------------------------------------------------------------
  557.  */
  558.  
  559. static void
  560. MakeSpace(hPtr, size)
  561.     HistoryEvent *hPtr;
  562.     int size;            /* # of bytes needed in hPtr. */
  563. {
  564.     if (hPtr->bytesAvl < size) {
  565.     ckfree(hPtr->command);
  566.     hPtr->command = (char *) ckalloc((unsigned) size);
  567.     hPtr->bytesAvl = size;
  568.     }
  569. }
  570.  
  571. /*
  572.  *----------------------------------------------------------------------
  573.  *
  574.  * InsertRev --
  575.  *
  576.  *    Add a new revision to the list of those pending for iPtr.
  577.  *    Do it in a way that keeps the revision list sorted in
  578.  *    increasing order of firstIndex.  Also, eliminate revisions
  579.  *    that are subsets of other revisions.
  580.  *
  581.  * Results:
  582.  *    None.
  583.  *
  584.  * Side effects:
  585.  *    RevPtr is added to iPtr's revision list.
  586.  *
  587.  *----------------------------------------------------------------------
  588.  */
  589.  
  590. static void
  591. InsertRev(iPtr, revPtr)
  592.     Interp *iPtr;            /* Interpreter to use. */
  593.     register HistoryRev *revPtr;    /* Revision to add to iPtr's list. */
  594. {
  595.     register HistoryRev *curPtr;
  596.     register HistoryRev *prevPtr;
  597.  
  598.     for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL;
  599.         prevPtr = curPtr, curPtr = curPtr->nextPtr) {
  600.     /*
  601.      * If this revision includes the new one (or vice versa) then
  602.      * just eliminate the one that is a subset of the other.
  603.      */
  604.  
  605.     if ((revPtr->firstIndex <= curPtr->firstIndex)
  606.         && (revPtr->lastIndex >= curPtr->firstIndex)) {
  607.         curPtr->firstIndex = revPtr->firstIndex;
  608.         curPtr->lastIndex = revPtr->lastIndex;
  609.         curPtr->newSize = revPtr->newSize;
  610.         ckfree(curPtr->newBytes);
  611.         curPtr->newBytes = revPtr->newBytes;
  612.         ckfree((char *) revPtr);
  613.         return;
  614.     }
  615.     if ((revPtr->firstIndex >= curPtr->firstIndex)
  616.         && (revPtr->lastIndex <= curPtr->lastIndex)) {
  617.         ckfree(revPtr->newBytes);
  618.         ckfree((char *) revPtr);
  619.         return;
  620.     }
  621.  
  622.     if (revPtr->firstIndex < curPtr->firstIndex) {
  623.         break;
  624.     }
  625.     }
  626.  
  627.     /*
  628.      * Insert revPtr just after prevPtr.
  629.      */
  630.  
  631.     if (prevPtr == NULL) {
  632.     revPtr->nextPtr = iPtr->revPtr;
  633.     iPtr->revPtr = revPtr;
  634.     } else {
  635.     revPtr->nextPtr = prevPtr->nextPtr;
  636.     prevPtr->nextPtr = revPtr;
  637.     }
  638. }
  639.  
  640. /*
  641.  *----------------------------------------------------------------------
  642.  *
  643.  * RevCommand --
  644.  *
  645.  *    This procedure is invoked by the "history" command to record
  646.  *    a command revision.  See the comments at the beginning of the
  647.  *    file for more information about revisions.
  648.  *
  649.  * Results:
  650.  *    None.
  651.  *
  652.  * Side effects:
  653.  *    Revision information is recorded.
  654.  *
  655.  *----------------------------------------------------------------------
  656.  */
  657.  
  658. static void
  659. RevCommand(iPtr, string)
  660.     register Interp *iPtr;    /* Interpreter in which to perform the
  661.                  * substitution. */
  662.     char *string;        /* String to substitute. */
  663. {
  664.     register HistoryRev *revPtr;
  665.  
  666.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  667.     return;
  668.     }
  669.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  670.     revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst;
  671.     revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst;
  672.     revPtr->newSize = strlen(string);
  673.     revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1));
  674.     strcpy(revPtr->newBytes, string);
  675.     InsertRev(iPtr, revPtr);
  676. }
  677.  
  678. /*
  679.  *----------------------------------------------------------------------
  680.  *
  681.  * RevResult --
  682.  *
  683.  *    This procedure is invoked by the "history" command to record
  684.  *    a result revision.  See the comments at the beginning of the
  685.  *    file for more information about revisions.
  686.  *
  687.  * Results:
  688.  *    None.
  689.  *
  690.  * Side effects:
  691.  *    Revision information is recorded.
  692.  *
  693.  *----------------------------------------------------------------------
  694.  */
  695.  
  696. static void
  697. RevResult(iPtr, string)
  698.     register Interp *iPtr;    /* Interpreter in which to perform the
  699.                  * substitution. */
  700.     char *string;        /* String to substitute. */
  701. {
  702.     register HistoryRev *revPtr;
  703.     char *evalFirst, *evalLast;
  704.     char *argv[2];
  705.  
  706.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  707.     return;
  708.     }
  709.  
  710.     /*
  711.      * Expand the replacement range to include the brackets that surround
  712.      * the command.  If there aren't any brackets (i.e. this command was
  713.      * invoked at top-level) then don't do any revision.  Also, if there
  714.      * are several commands in brackets, of which this is just one,
  715.      * then don't do any revision.
  716.      */
  717.  
  718.     evalFirst = iPtr->evalFirst;
  719.     evalLast = iPtr->evalLast + 1;
  720.     while (1) {
  721.     if (evalFirst == iPtr->historyFirst) {
  722.         return;
  723.     }
  724.     evalFirst--;
  725.     if (*evalFirst == '[') {
  726.         break;
  727.     }
  728.     if (!isspace(UCHAR(*evalFirst))) {
  729.         return;
  730.     }
  731.     }
  732.     if (*evalLast != ']') {
  733.     return;
  734.     }
  735.  
  736.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  737.     revPtr->firstIndex = evalFirst - iPtr->historyFirst;
  738.     revPtr->lastIndex = evalLast - iPtr->historyFirst;
  739.     argv[0] = string;
  740.     revPtr->newBytes = Tcl_Merge(1, argv);
  741.     revPtr->newSize = strlen(revPtr->newBytes);
  742.     InsertRev(iPtr, revPtr);
  743. }
  744.  
  745. /*
  746.  *----------------------------------------------------------------------
  747.  *
  748.  * DoRevs --
  749.  *
  750.  *    This procedure is called to apply the history revisions that
  751.  *    have been recorded in iPtr.
  752.  *
  753.  * Results:
  754.  *    None.
  755.  *
  756.  * Side effects:
  757.  *    The most recent entry in the history for iPtr may be modified.
  758.  *
  759.  *----------------------------------------------------------------------
  760.  */
  761.  
  762. static void
  763. DoRevs(iPtr)
  764.     register Interp *iPtr;    /* Interpreter whose history is to
  765.                  * be modified. */
  766. {
  767.     register HistoryRev *revPtr;
  768.     register HistoryEvent *eventPtr;
  769.     char *newCommand, *p;
  770.     unsigned int size;
  771.     int bytesSeen, count;
  772.  
  773.     if (iPtr->revPtr == NULL) {
  774.     return;
  775.     }
  776.  
  777.     /*
  778.      * The revision is done in two passes.  The first pass computes the
  779.      * amount of space needed for the revised event, and the second pass
  780.      * pieces together the new event and frees up the revisions.
  781.      */
  782.  
  783.     eventPtr = &iPtr->events[iPtr->curEvent];
  784.     size = strlen(eventPtr->command) + 1;
  785.     for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) {
  786.     size -= revPtr->lastIndex + 1 - revPtr->firstIndex;
  787.     size += revPtr->newSize;
  788.     }
  789.  
  790.     newCommand = (char *) ckalloc(size);
  791.     p = newCommand;
  792.     bytesSeen = 0;
  793.     for (revPtr = iPtr->revPtr; revPtr != NULL; ) {
  794.     HistoryRev *nextPtr = revPtr->nextPtr;
  795.  
  796.     count = revPtr->firstIndex - bytesSeen;
  797.     if (count > 0) {
  798.         strncpy(p, eventPtr->command + bytesSeen, count);
  799.         p += count;
  800.     }
  801.     strncpy(p, revPtr->newBytes, revPtr->newSize);
  802.     p += revPtr->newSize;
  803.     bytesSeen = revPtr->lastIndex+1;
  804.     ckfree(revPtr->newBytes);
  805.     ckfree((char *) revPtr);
  806.     revPtr = nextPtr;
  807.     }
  808.     if (&p[strlen(&eventPtr->command[bytesSeen]) + 1] >
  809.         &newCommand[size]) {
  810. #ifdef macintosh
  811.     mac_printf("Assertion failed!\n");
  812. #else
  813.     printf("Assertion failed!\n");
  814. #endif
  815.     }
  816.     strcpy(p, eventPtr->command + bytesSeen);
  817.  
  818.     /*
  819.      * Replace the command in the event.
  820.      */
  821.  
  822.     ckfree(eventPtr->command);
  823.     eventPtr->command = newCommand;
  824.     eventPtr->bytesAvl = size;
  825.     iPtr->revPtr = NULL;
  826. }
  827.  
  828. /*
  829.  *----------------------------------------------------------------------
  830.  *
  831.  * GetEvent --
  832.  *
  833.  *    Given a textual description of an event (see the manual page
  834.  *    for legal values) find the corresponding event and return its
  835.  *    command string.
  836.  *
  837.  * Results:
  838.  *    The return value is a pointer to the event named by "string".
  839.  *    If no such event exists, then NULL is returned and an error
  840.  *    message is left in iPtr.
  841.  *
  842.  * Side effects:
  843.  *    None.
  844.  *
  845.  *----------------------------------------------------------------------
  846.  */
  847.  
  848. static HistoryEvent *
  849. GetEvent(iPtr, string)
  850.     register Interp *iPtr;    /* Interpreter in which to look. */
  851.     char *string;        /* Description of event. */
  852. {
  853.     int eventNum, index;
  854.     register HistoryEvent *eventPtr;
  855.     int length;
  856.  
  857.     /*
  858.      * First check for a numeric specification of an event.
  859.      */
  860.  
  861.     if (isdigit(UCHAR(*string)) || (*string == '-')) {
  862.     if (Tcl_GetInt((Tcl_Interp *) iPtr, string, &eventNum) != TCL_OK) {
  863.         return NULL;
  864.     }
  865.     if (eventNum < 0) {
  866.         eventNum += iPtr->curEventNum;
  867.         }
  868.     if (eventNum > iPtr->curEventNum) {
  869.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  870.             "\" hasn't occurred yet", (char *) NULL);
  871.         return NULL;
  872.     }
  873.     if ((eventNum <= iPtr->curEventNum-iPtr->numEvents)
  874.         || (eventNum <= 0)) {
  875.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  876.             "\" is too far in the past", (char *) NULL);
  877.         return NULL;
  878.     }
  879.     index = iPtr->curEvent + (eventNum - iPtr->curEventNum);
  880.     if (index < 0) {
  881.         index += iPtr->numEvents;
  882.     }
  883.     return &iPtr->events[index];
  884.     }
  885.  
  886.     /*
  887.      * Next, check for an event that contains the string as a prefix or
  888.      * that matches the string in the sense of Tcl_StringMatch.
  889.      */
  890.  
  891.     length = strlen(string);
  892.     for (index = iPtr->curEvent - 1; ; index--) {
  893.     if (index < 0) {
  894.         index += iPtr->numEvents;
  895.     }
  896.     if (index == iPtr->curEvent) {
  897.         break;
  898.     }
  899.     eventPtr = &iPtr->events[index];
  900.     if ((strncmp(eventPtr->command, string, length) == 0)
  901.         || Tcl_StringMatch(eventPtr->command, string)) {
  902.         return eventPtr;
  903.     }
  904.     }
  905.  
  906.     Tcl_AppendResult((Tcl_Interp *) iPtr, "no event matches \"", string,
  907.         "\"", (char *) NULL);
  908.     return NULL;
  909. }
  910.  
  911. /*
  912.  *----------------------------------------------------------------------
  913.  *
  914.  * SubsAndEval --
  915.  *
  916.  *    Generate a new command by making a textual substitution in
  917.  *    the "cmd" argument.  Then execute the new command.
  918.  *
  919.  * Results:
  920.  *    The return value is a standard Tcl error.
  921.  *
  922.  * Side effects:
  923.  *    History gets revised if the substitution is occurring on
  924.  *    a recorded command line.  Also, the re-executed command
  925.  *    may produce side-effects.
  926.  *
  927.  *----------------------------------------------------------------------
  928.  */
  929.  
  930. static int
  931. SubsAndEval(iPtr, cmd, old, new)
  932.     register Interp *iPtr;    /* Interpreter in which to execute
  933.                  * new command. */
  934.     char *cmd;            /* Command in which to substitute. */
  935.     char *old;            /* String to search for in command. */
  936.     char *new;            /* Replacement string for "old". */
  937. {
  938.     char *src, *dst, *newCmd;
  939.     int count, oldLength, newLength, length, result;
  940.  
  941.     /*
  942.      * Figure out how much space it will take to hold the
  943.      * substituted command (and complain if the old string
  944.      * doesn't appear in the original command).
  945.      */
  946.  
  947.     oldLength = strlen(old);
  948.     newLength = strlen(new);
  949.     src = cmd;
  950.     count = 0;
  951.     while (1) {
  952.     src = strstr(src, old);
  953.     if (src == NULL) {
  954.         break;
  955.     }
  956.     src += oldLength;
  957.     count++;
  958.     }
  959.     if (count == 0) {
  960.     Tcl_AppendResult((Tcl_Interp *) iPtr, "\"", old,
  961.         "\" doesn't appear in event", (char *) NULL);
  962.     return TCL_ERROR;
  963.     }
  964.     length = strlen(cmd) + count*(newLength - oldLength);
  965.  
  966.     /*
  967.      * Generate a substituted command.
  968.      */
  969.  
  970.     newCmd = (char *) ckalloc((unsigned) (length + 1));
  971.     dst = newCmd;
  972.     while (1) {
  973.     src = strstr(cmd, old);
  974.     if (src == NULL) {
  975.         strcpy(dst, cmd);
  976.         break;
  977.     }
  978.     strncpy(dst, cmd, src-cmd);
  979.     dst += src-cmd;
  980.     strcpy(dst, new);
  981.     dst += newLength;
  982.     cmd = src + oldLength;
  983.     }
  984.  
  985.     RevCommand(iPtr, newCmd);
  986.     result = Tcl_Eval((Tcl_Interp *) iPtr, newCmd);
  987.     ckfree(newCmd);
  988.     return result;
  989. }
  990.  
  991. /*
  992.  *----------------------------------------------------------------------
  993.  *
  994.  * GetWords --
  995.  *
  996.  *    Given a command string, return one or more words from the
  997.  *    command string.
  998.  *
  999.  * Results:
  1000.  *    The return value is a pointer to a dynamically-allocated
  1001.  *    string containing the words of command specified by "words".
  1002.  *    If the word specifier has improper syntax then an error
  1003.  *    message is placed in iPtr->result and NULL is returned.
  1004.  *
  1005.  * Side effects:
  1006.  *    Memory is allocated.  It is the caller's responsibilty to
  1007.  *    free the returned string..
  1008.  *
  1009.  *----------------------------------------------------------------------
  1010.  */
  1011.  
  1012. static char *
  1013. GetWords(iPtr, command, words)
  1014.     register Interp *iPtr;    /* Tcl interpreter in which to place
  1015.                  * an error message if needed. */
  1016.     char *command;        /* Command string. */
  1017.     char *words;        /* Description of which words to extract
  1018.                  * from the command.  Either num[-num] or
  1019.                  * a pattern. */
  1020. {
  1021.     char *result;
  1022.     char *start, *end, *dst;
  1023.     register char *next;
  1024.     int first;            /* First word desired. -1 means last word
  1025.                  * only. */
  1026.     int last;            /* Last word desired.  -1 means use everything
  1027.                  * up to the end. */
  1028.     int index;            /* Index of current word. */
  1029.     char *pattern;
  1030.  
  1031.     /*
  1032.      * Figure out whether we're looking for a numerical range or for
  1033.      * a pattern.
  1034.      */
  1035.  
  1036.     pattern = NULL;
  1037.     first = 0;
  1038.     last = -1;
  1039.     if (*words == '$') {
  1040.     if (words[1] != '\0') {
  1041.         goto error;
  1042.     }
  1043.     first = -1;
  1044.     } else if (isdigit(UCHAR(*words))) {
  1045.     first = strtoul(words, &start, 0);
  1046.     if (*start == 0) {
  1047.         last = first;
  1048.     } else if (*start == '-') {
  1049.         start++;
  1050.         if (*start == '$') {
  1051.         start++;
  1052.         } else if (isdigit(UCHAR(*start))) {
  1053.         last = strtoul(start, &start, 0);
  1054.         } else {
  1055.         goto error;
  1056.         }
  1057.         if (*start != 0) {
  1058.         goto error;
  1059.         }
  1060.     }
  1061.     if ((first > last) && (last != -1)) {
  1062.         goto error;
  1063.     }
  1064.     } else {
  1065.     pattern = words;
  1066.     }
  1067.  
  1068.     /*
  1069.      * Scan through the words one at a time, copying those that are
  1070.      * relevant into the result string.  Allocate a result area large
  1071.      * enough to hold all the words if necessary.
  1072.      */
  1073.  
  1074.     result = (char *) ckalloc((unsigned) (strlen(command) + 1));
  1075.     dst = result;
  1076.     for (next = command; isspace(UCHAR(*next)); next++) {
  1077.     /* Empty loop body:  just find start of first word. */
  1078.     }
  1079.     for (index = 0; *next != 0; index++) {
  1080.     start = next;
  1081.     end = TclWordEnd(next, 0);
  1082.     if (*end != 0) {
  1083.         end++;
  1084.         for (next = end; isspace(UCHAR(*next)); next++) {
  1085.         /* Empty loop body:  just find start of next word. */
  1086.         }
  1087.     }
  1088.     if ((first > index) || ((first == -1) && (*next != 0))) {
  1089.         continue;
  1090.     }
  1091.     if ((last != -1) && (last < index)) {
  1092.         continue;
  1093.     }
  1094.     if (pattern != NULL) {
  1095.         int match;
  1096.         char savedChar = *end;
  1097.  
  1098.         *end = 0;
  1099.         match = Tcl_StringMatch(start, pattern);
  1100.         *end = savedChar;
  1101.         if (!match) {
  1102.         continue;
  1103.         }
  1104.     }
  1105.     if (dst != result) {
  1106.         *dst = ' ';
  1107.         dst++;
  1108.     }
  1109.     strncpy(dst, start, (end-start));
  1110.     dst += end-start;
  1111.     }
  1112.     *dst = 0;
  1113.  
  1114.     /*
  1115.      * Check for an out-of-range argument index.
  1116.      */
  1117.  
  1118.     if ((last >= index) || (first >= index)) {
  1119.     ckfree(result);
  1120.     Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words,
  1121.         "\" specified non-existent words", (char *) NULL);
  1122.     return NULL;
  1123.     }
  1124.     return result;
  1125.  
  1126.     error:
  1127.     Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words,
  1128.         "\":  should be num-num or pattern", (char *) NULL);
  1129.     return NULL;
  1130. }
  1131.